Skip to content顺序与
概述
将两个对象数组合并,以每个对象的 id 字段作为合并键。id 相同的对象,后面数组的属性覆盖前面数组的同名属性;id 不同的对象全部保留。该需求常见于数据同步与批量更新场景。
基本概念
- id:合并依据的唯一标识字段。题目保证单个数组内
id无重复,但合并逻辑本身不依赖这一假设。 - 合并语义:
arr2被视为较新版本。当同一id出现多次时,后出现的对象属性覆盖先出现的同名属性。 - 浅合并:合并仅处理对象自身的可枚举属性,不会递归合并嵌套对象。
工作原理
将两个数组拼接后,用 reduce 将每条记录归并到以 id 为键的累加对象(或 Map)中。归并逻辑如下:
js
pre[cur.id] = pre[cur.id] ? { ...pre[cur.id], ...cur } : cur;展开运算符的执行顺序决定了 cur 的属性总会覆盖已有对象中的同名属性。若某个 id 首次出现,直接存入当前对象;再次出现时,现有对象被浅拷贝,再用新对象的属性覆盖。
最后通过 Object.values()(或 map.values())提取值构成结果数组。拼接操作仅创建新数组引用,不影响整体遍历次数。
基本用法
一种基于普通对象的实现:
js
function join(arr1, arr2) {
const idMap = [...arr1, ...arr2].reduce((acc, item) => {
const id = item.id;
acc[id] = acc[id] ? { ...acc[id], ...item } : item;
return acc;
}, {});
return Object.values(idMap);
}调用示例:
js
const arr1 = [
{ id: 1, x: 2, y: 3 },
{ id: 2, x: 3, y: 6 },
];
const arr2 = [
{ id: 2, x: 10, y: 20 },
{ id: 3, x: 0, y: 0 },
];
join(arr1, arr2);
// [
// { id: 1, x: 2, y: 3 },
// { id: 2, x: 10, y: 20 },
// { id: 3, x: 0, y: 0 }
// ]id 为 2 的对象在合并后,x 和 y 均取自 arr2;id 为 1 和 3 的对象则分别原样来自 arr1 和 arr2。
示例
顺序与 Map
普通对象作为累加器时,Object.values() 返回的值的顺序在不同引擎上存在差异。在 V8 引擎中,数字键会按数值升序排列,可能导致输出顺序既不是 arr1 的顺序,也不是 arr2 的顺序:
js
const arr1 = [{ id: 3, a: 3 }, { id: 1, a: 1 }];
const arr2 = [{ id: 2, a: 2 }];
join(arr1, arr2);
// 在 V8 中可能得到:
// [{ id: 1, a: 1 }, { id: 2, a: 2 }, { id: 3, a: 3 }]若需要保持数据在原始数组中的首次出现顺序,应使用 Map。Map 的迭代顺序即插入顺序:
js
function joinStable(arr1, arr2) {
const map = new Map();
for (const item of arr1) {
map.set(item.id, { ...item });
}
for (const item of arr2) {
const existing = map.get(item.id);
map.set(item.id, existing ? { ...existing, ...item } : { ...item });
}
return [...map.values()];
}此时输出顺序由 arr1 各 id 首次出现的位置决定,arr2 中新的 id 追加在末尾。
边界输入
若任一个输入为空数组,结果即为另一个数组的浅拷贝。若同一数组内出现重复 id(题目保证不出现,但逻辑上可处理),后出现的条目同样会覆盖先出现的条目,符合“后出优先”的语义。
注意点
- 键类型:使用普通对象作为累加器时,所有键都会被转换为字符串。
id为数字1和字符串"1"时会被视为同一个键。若需区分类型,应改用Map。 - 浅合并的局限:展开语法只复制对象自身可枚举属性,嵌套对象会被整个替换,不会递归合并。
- 时间复杂度:O(n + m),空间消耗与合并后不同
id的数量成正比。 - 属性来源:展开语法只处理对象自身可枚举属性,原型链上的属性不会被拷贝。
限制
- 仅适用对象数组,且基于单一字段合并,不支持复合键。
- 合并策略固定为覆盖,若需要冲突检测或自定义合并逻辑,需另行实现。
- 使用普通对象时,无法保证输出顺序与任意原始数组一致;顺序敏感的场景必须显式处理。
应用
- 同步来自多个数据源的记录列表,例如客户端本地缓存与服务端最新快照的合并。
- 分层配置覆盖:默认配置、环境配置与运行时参数叠加。
- 将增量更新数据合并到全量快照中,形成最新状态。
